"""
Task Monitor window

Browses:
  - Running agents, allowing to reinitiate or finish an agent execution
  - Threads, browsing only

"""

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import types
import io
import os
import threading

import ui_w_taskmonitor
from agent import recorders
import agent
from misc import fileobserver
from misc import misc
import system.navalasystem as na


try:
    _fromUtf8 = QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s


M_PROCESS = 0
M_THREAD = 1

class TaskMonitor(QMainWindow, ui_w_taskmonitor.Ui_TaskMonitor):
    def __init__(self, parent=None):
        super(TaskMonitor, self).__init__(parent)
        self.setupUi(self)
        self.InitializeUI0()

        self.currentProcess = None
        self.currentThread = None
        #self.resize(1, 1) # As small as possible!
        

    def InitializeUI0(self):

        a = [self.tableWidgetProcess, self.tableWidgetThread]
        for t in a:
            t.setAlternatingRowColors(True)
            t.setEditTriggers(QTableWidget.NoEditTriggers)
            
        self.fileObserver = fileobserver.FileObserver()
        self.fileObserver.minInterval = 0.25
        self.fileObserver.FileChanged.connect(self.on_FileChanged)
        self.fileObserver.start()

        na.vala.pm.ProcessesChanged.connect(self.on_ProcessesChanged)
        #na.vala.ProcessLogAdded.connect(self.on_ProcessLogAdded)
        
        
    def GetMode(self):
        """Returns M_PROCESS or M_THREAD."""
        return self.tabWidget.currentIndex()
        
    def GetCurrentTask(self):
        """Returns either a process or a thread, or None."""
        return self.currentProcess if self.GetMode() == M_PROCESS \
               else self.currentThread

    ############################################################################
    ## SLOTS
    
    def on_ProcessesChanged(self):
        self.PopulateProcesses()

    def on_FileChanged(self, fns):
        fns = self.fileObserver.filenames
        if fns and os.path.exists(fns[0]):
            with file(fns[0]) as f:
                if self.checkBoxTail.isChecked():
                    s = misc.tail(f, self.spinBoxTailSize.value())
                else:
                    s = f.read()
        else:
            s = "Nothing to display yet"
        te = self.textEditLog
        te.setText(s)
        te.moveCursor(QTextCursor.End)

    @pyqtSignature("")
    def on_pushButtonRefresh_clicked(self):
        if self.GetMode() == M_PROCESS:
            self.PopulateProcesses()
        else:
            self.PopulateThreads()
        
    @pyqtSignature("")
    def on_pushButtonReload_clicked(self):
        self.on_FileChanged([])

    @pyqtSignature("")
    def on_pushButtonPutMessage_clicked(self):
        obj = self.GetCurrentTask()
        if obj is not None:
            try:
                token = str(self.lineEditPToken.text())
                sData = str(self.lineEditPData.text())
                if len(sData) == 0 or sData.isspace():
                    data = True
                else:
                    data = eval(sData)
                obj.PutMessage(token, data)
            except Exception as E:
                QMessageBox.critical(self, "Send message", E.message)
                raise
        else:
            QMessageBox.warning(self, "No task selected", "No process/thread selected")
    
    @pyqtSignature("")
    def on_pushButtonExit_clicked(self):
        if self.currentProcess:
            self.currentProcess.Exit()
        
    @pyqtSignature("QTableWidgetItem*, QTableWidgetItem*")
    def on_tableWidgetProcess_currentItemChanged(self, curr, prev):
        self.UpdateCurrentProcess(curr)

    @pyqtSignature("QTableWidgetItem*, QTableWidgetItem*")
    def on_tableWidgetThread_currentItemChanged(self, curr, prev):
        self.UpdateCurrentThread(curr)
    
    @pyqtSignature("int")
    def on_tabWidget_currentChanged(self, index):
        if index == M_PROCESS:
            self.PopulateProcesses()
        else:
            self.PopulateThreads()
    
    def closeEvent(self, event):
        self.fileObserver.Exit()
        
    ############################################################################
    
    def PopulateProcesses(self):
        pp = na.vala.pm.processes
        n = len(pp)
        t = self.tableWidgetProcess
        t.clear()
        t.sortItems(-1)
        t.setRowCount(n)
        t.setColumnCount(4)
        t.setHorizontalHeaderLabels(["Id", "Name", "Class", "Time registered"])

        for (i, (process_, data)) in enumerate(pp.iteritems()):
            item = QTableWidgetItem("%03d" % data["id"])
            item.setData(Qt.UserRole, QVariant(process_.id))
            t.setItem(i, 0, item)
            item = QTableWidgetItem(process_.name)
            t.setItem(i, 1, item)
            item = QTableWidgetItem(process_.__class__.__name__)
            t.setItem(i, 2, item)
            item = QTableWidgetItem(
                data["timeStarted"].strftime("%Y/%m/%d %H:%M"))
            # item.setTextAlignment(Qt.AlignCenter)
            t.setItem(i, 3, item)
        
        t.resizeColumnsToContents()
        
        if n > 0:
            t.setCurrentCell(0, 0)
            self.UpdateCurrentProcess()
    
    def PopulateThreads(self):
        tt = threading.enumerate()
        n = len(tt)
        t = self.tableWidgetThread
        t.clear()
        t.sortItems(-1)
        t.setRowCount(n)
        t.setColumnCount(2)
        t.setHorizontalHeaderLabels(["Name", "Class"])

        for (i, t_) in enumerate(tt):
            item = QTableWidgetItem(t_.name)
            t.setItem(i, 0, item)
            item = QTableWidgetItem(t_.__class__.__name__)
            t.setItem(i, 1, item)
        
        t.resizeColumnsToContents()
        
        if n > 0:
            t.setCurrentCell(0, 0)
            self.UpdateCurrentThread()

    def UpdateCurrentProcess(self, curr=None):
        """Updates internal and GUI status after current process has changed.
        
        curr is passed from currentItemChanged, because the signal is emitted
        before the item has internally changed."""
        t = self.tableWidgetProcess
        if curr is None:
            items = t.selectedItems()
            if len(items) > 0:
                curr = items[0]
        else:
            curr = t.item(curr.row(), 0) # Gets item of first column instead
        if curr is not None:
            id = curr.data(Qt.UserRole).toPyObject()
            (process_, data) = na.vala.pm.process_GetById(id)
            self.currentProcess = process_
            self.labelFilename.setText(process_.logFilename)
            self.pushButtonReload.setEnabled(True)
            self.fileObserver.SetFilenames([process_.logFilename])
            self.on_FileChanged([])
        else:
            self.currentProcess = None

    def UpdateCurrentThread(self, curr=None):
        """Updates internal and GUI status after current Thread has changed.
        
        curr is passed from currentItemChanged, because the signal is emitted
        before the item has internally changed."""
        t = self.tableWidgetThread
        if curr is None:
            items = t.selectedItems()
            if len(items) > 0:
                curr = items[0]
        else:
            curr = t.item(curr.row(), 0) # Gets item of first column instead
        if curr is not None:
            name = curr.data(0).toString() # name
            t = self.thread_GetByName(name)
            self.currentThread = t
            if t is not None and hasattr(t, "logFilename"):
                self.labelFilename.setText(t.logFilename)
                self.pushButtonReload.setEnabled(True)
                self.fileObserver.SetFilenames([t.logFilename])
                self.on_FileChanged([])
        else:
            self.currentThread = None

    def thread_GetByName(self, name):
        tt = threading.enumerate()
        for t in tt:
            if t.name == name:
                return t
        return None
